//==================================================================
//  Copyright (C) 2006  Davide Pasca
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//==================================================================
///
///
///
///
///
//==================================================================

#include <Windows.h>
#include <stdlib.h>
#include <memory>
#include <string>
#include <gl/glew.h>
#include "appbase3.h"
#include "ShadowBuffer.h"
#include "pgamepad.h"
#include "..\converters\read3ds\read3ds.h"
#include "TestShader.h"
#include "ShadowCastShader.h"

//==================================================================
#define	NEAR_Z	10
#define	FAR_Z	3500

//==================================================================
using namespace PMath;

//==================================================================
static ShadowBuffer	_app;

//==================================================================
bool main_start( void *hinstance )
{
	u_int	desk_wd, desk_he;

	WinSys::Init( (HINSTANCE)hinstance, NULL, _T("SHATEST") );
	WinSys::GetDeskSize( desk_wd, desk_he );

	int	win_wd = desk_wd * 3 / 4;
	int	win_he = desk_he * 3 / 4;

	_app._winp = new Window( _T("ShadowBuffer - Pascalibs demo by Davide Pasca"),
							 NULL,
							 &_app,
							 ShadowBuffer::handleWinEvent_s, 
							 (desk_wd - win_wd) / 2,
							 (desk_he - win_he) / 2,
							 win_wd,
							 win_he,
							 (win_init_flags)(WIN_INIT_FLG_OPENGL | WIN_INIT_FLG_DONT_CLEAR) );

	_app._winp->SetMSWindowsEventFilter( ShadowBuffer::handleMSWinEvent_s );

	return true;
}

//==================================================================
void main_anim(void)
{
	_app.onAnim();
	_app._winp->Invalidate();
}

//==================================================================
int main_requestquit(void)
{
	return 0;
}

//==================================================================
void main_quit(void)
{

}

//==================================================================
int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
	main_start( hInstance );
	appbase_mainloop();
	main_quit();

	return 0;
}

//==================================================================
void ShadowBuffer::doPaint( int vp_wd, int vp_he )
{
	// eliminate the skybox temporarily
	_skyboxp->SetBranchVisibility( false );
	_shadow_taskp->RenderLightView( _SunLightp );
	_skyboxp->SetBranchVisibility( true );

	// select the camera for the render-scene
	_RScenep->SelectCamera( _Camerap );
	_RScenep->UpdatePose();
	_Camerap->SetViewport( 0, 0, vp_wd, vp_he, 0, 1 );

	TextureBase	&DepthTexture = _shadow_taskp->GetRendTarget()->GetDepthTexture();


	_RDevice->RenderBegin();

	//------------------------
	RendM5D::Params	rparams_standard;

	_RDevice->_DrawState.SetZTest( true );
	_RScenep->PreRender( rparams_standard );
	_RScenep->Render( rparams_standard );

	//------------------------
	RendM5D::Params	rparams_plain;

	rparams_plain.SetPlain( _ShadowCastShaderp->GetNamedVertexSHParam( "proj_obj_m" ),
							_ShadowCastShaderp->GetNamedVertexSHParam( "view_obj_m" ),
							_shadow_taskp->GetLightCamera(),
							_ShadowCastShaderp->GetNamedVertexSHParam( "lproj_obj_m" ) );

	_ShadowCastShaderp->SetDepthTexture( DepthTexture );

	_RDevice->_ShaderMng.SelectVertexShader( _ShadowCastShaderp->GetVtxShader() );
	_RDevice->_ShaderMng.SelectPixelShader( _ShadowCastShaderp->GetPixShader() );


	_skyboxp->SetBranchVisibility( false );
	_RScenep->PreRender( rparams_plain );

	_RDevice->_DrawState.SetBlending(	true,
										PSYSGFX::BLENDFCT_SRC_ALPHA,
										PSYSGFX::BLENDFCT_ONE_MINUS_SRC_ALPHA );

	_RScenep->Render( rparams_plain );
	_skyboxp->SetBranchVisibility( true );

	GXY::SelectContext( _mygxy_ctxap );
	GXY::SetViewport( 0, 0, vp_wd, vp_he );
	GXY::SetBlend( PSYSGFX::BASICBLEND_OPAQUE );
	GXY::SetVertRGB( 255, 255, 255 );	

	glBindTexture( GL_TEXTURE_2D, DepthTexture._tex_ogl_id );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE );

	GXY::SelectTexture( &DepthTexture );
	GXY::PrimBegin( GXY::PRIM_QUADS );
	GXY::SetVertTxcoord( 0, 1 ); GXY::SetVertPos( 0, 0 );
	GXY::SetVertTxcoord( 1, 1 ); GXY::SetVertPos( 256, 0 );
	GXY::SetVertTxcoord( 1, 0 ); GXY::SetVertPos( 256, 256 );
	GXY::SetVertTxcoord( 0, 0 ); GXY::SetVertPos( 0, 256 );
	GXY::PrimEnd();

	GXY::Flush();
	glBindTexture( GL_TEXTURE_2D, DepthTexture._tex_ogl_id );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB );

	_RDevice->RenderEnd();
}

//==================================================================
void ShadowBuffer::onCreation( Window *winp, int vp_wd, int vp_he )
{
	_RDevice = RendSystem::NewRendDevice_s( vp_wd, vp_he, _T(".") );
	_mygxy_ctxap = GXY::CreateContext( _RDevice, vp_wd, vp_he, (float)vp_wd / vp_he );

	_RScenep = new RendM5D::Scene( _RDevice, _T("ShadowBuffer Scene"), _T(".\\data\\") );

	_SunLightp = _RScenep->NewLight( _T("Sun light") );
	_SunLightp->SetColor( Vector3( 8, 8, 8 ) );
	//Vector4	ldir = Vector4( .69f, .7f, .65f, 0).GetNormalized3();
	Vector4	ldir = Vector4( .69f, .7f, .65f, 0).GetNormalized3();
	_SunLightp->SetLocalPos( ldir );

	// camera for the scene
	_Camerap = _RScenep->NewCamera( _T("Camera") );
	_Camerap->SetViewport( 0, 0, vp_wd, vp_he, 0, 1 );
	_Camerap->SetPerspectiveProj( M_PI/3, (float)vp_wd/vp_he, NEAR_Z, FAR_Z );
	//_Camerap->SetOrthogonalProj( -1000, 1000, -1000, 1000, NEAR_Z, FAR_Z );

	_skyboxp = new SkyBox( _RScenep );
	_seabedp = new SeaBed( _RScenep );

	_plane_rootp = _RScenep->NewModel( M5D::Model::MT_NULL, _T("Airplane Base"), NULL );
	Read3DS( _RScenep, PUtils::Memfile( _T("data\\a3dsemi.3ds") ), _plane_rootp ).DoRead();

	Vector3	bbox_min, bbox_max;
	_plane_rootp->GetBranchBBox( bbox_min, bbox_max );

	_CameraFly.Setup( 0, 25, 300, 0, 0, 0 );

	_shadow_taskp = new ShadowTask( _RDevice, _RScenep, NEAR_Z, FAR_Z );

	_TestShaderp = new TestShader( _RDevice, _RScenep );
	_ShadowCastShaderp = new ShadowCastShader( _RDevice, _RScenep );
}

//==================================================================
void ShadowBuffer::onAnim()
{
	_plane_rootp->SetMatrix( Matrix44(Matrix44::IDENTITY) );

	float	lstick[2];
	float	rstick[2];

	//gamepadp->GetSticks( 0, lstick, rstick );

	//==================================================================
	lstick[0] = (GetAsyncKeyState( 'A' ) & 0x8000) ?  -1 :
				((GetAsyncKeyState( 'D' ) & 0x8000) ?  1 :
				0);


	lstick[1] = (GetAsyncKeyState( 'W' ) & 0x8000) ?  1 :
				((GetAsyncKeyState( 'S' ) & 0x8000) ? -1 :
				0);

	rstick[0] = (GetAsyncKeyState( VK_LEFT ) & 0x8000) ?  -1 :
				((GetAsyncKeyState( VK_RIGHT ) & 0x8000) ?  1 :
				0);


	rstick[1] = (GetAsyncKeyState( VK_UP ) & 0x8000) ?  1 :
				((GetAsyncKeyState( VK_DOWN ) & 0x8000) ? -1 :
				0);

	_CameraFly.Update( lstick[0]*5, 0, -lstick[1]*5, -rstick[1], -rstick[0], 0 );

	if ( _skyboxp )		_skyboxp->Update( *_CameraFly.GetMatrix().GetRow3(3) );
	if ( _seabedp )		_seabedp->Update();

	_Camerap->SetMatrix( _CameraFly.GetMatrix().GetBackTransformed() );
}

//==================================================================
int	ShadowBuffer::handleWinEvent( WindowEvent::Type etype, WindowEvent *eventp )
{
	switch ( etype )
	{
	case WindowEvent::ETYPE_CREATE:
		onCreation( eventp->winp, eventp->winp->GetWidth(), eventp->winp->GetHeight() );
		break;

	case WindowEvent::ETYPE_PAINT:
		doPaint( eventp->winp->GetWidth(), eventp->winp->GetHeight() );
		return 1;

	case WindowEvent::ETYPE_WINRESIZE:
		doPaint( eventp->winp->GetWidth(), eventp->winp->GetHeight() );
		return 1;

	case WindowEvent::ETYPE_DESTROY:
		PostQuitMessage( 0 );
		break;
	}

	return 0;
}

//==================================================================
int ShadowBuffer::handleMSWinEvent( class Window *T, HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
	if ( message == WM_DROPFILES )
	{
		HDROP			hDrop = (HDROP)wParam;
		wchar_t			filepath[8192];

		RendM5D::Scene	*new_scenep	= NULL;

		int	 n = DragQueryFile( hDrop, 0xffffffff, NULL, 0 );
		if ( n > 0 )
		{
			for (int i=0; i < n; ++i)
			{
				if ( DragQueryFile( hDrop, i, filepath, 8192-1 ) > 0 )
				{
					//if NOT( new_scenep = load_scene( filepath, "dragged file", new_scenep ) )
					//{
					//	return PERROR;
					//}
				}
			}

			SAFE_DELETE( _RScenep );

			_RScenep = new_scenep;
			//rebuild_menu( _menup, _scenep );
		}

		DragFinish( hDrop );
	}

	return POK;
}